// CoreCalc, a spreadsheet core implementation 

// ----------------------------------------------------------------------
// Copyright (c) 2006 Thomas S. Iversen

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Management;
using System.Text;
using System.Xml;
using Microsoft.CSharp;         // CSharpCodeProvider
using System.CodeDom.Compiler;  // CompileParameters
using System.Reflection;        // Assembly

// Infrastructure for running automated benchmarks

namespace CoreCalc {
  public class Benchmark {
    private XmlDocument xmldoc;
    private XmlElement xmlroot;

    public bool Save(String filename) {
      try {
        xmldoc.Save(filename);
        return true;
      } catch (Exception e) {
        Console.WriteLine(e.Message);
        return false;
      }
    }

    public XmlElement AddDataSet(String name, String units) {
      XmlElement xmldataset = xmldoc.CreateElement("", "DataSet", "");
      xmldataset.SetAttribute("Name", name);
      xmldataset.SetAttribute("Units", units);
      xmlroot.AppendChild(xmldataset);
      return xmldataset;
    }

    public XmlElement AddData(XmlElement xmldataset, String name) {
      XmlElement xmldata = xmldoc.CreateElement("", "Data", "");
      xmldata.SetAttribute("Name", name);
      xmldataset.AppendChild(xmldata);
      return xmldata;
    }

    public XmlElement AddSubData(XmlElement xmldata, String name, String units, int numberOfRuns) {
      XmlElement xmlsubdata = xmldoc.CreateElement("", "SubData", "");
      xmlsubdata.SetAttribute("Name", name);
      xmlsubdata.SetAttribute("Units", units);
      xmlsubdata.SetAttribute("NumberOfRuns", numberOfRuns.ToString());
      xmldata.AppendChild(xmlsubdata);
      return xmlsubdata;
    }

    public void AddRun(XmlElement xmlsubdata, int runNumber, String runData) {
      XmlElement xmlsubdatarun = xmldoc.CreateElement("", "Run", "");
      xmlsubdatarun.SetAttribute("RunNumber", runNumber.ToString());
      xmlsubdatarun.AppendChild(xmldoc.CreateTextNode(runData));
      xmlsubdata.AppendChild(xmlsubdatarun);
    }

    public Benchmark(String title, String author) {
      XmlElement xmlmetaroot;
      XmlNode xmlnode;
      XmlElement xmlchild;

      xmldoc = new XmlDocument();
      // Add the XML declaration section
      xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration, "", "");

      xmldoc.AppendChild(xmlnode);
      // Add the root element
      xmlroot = xmldoc.CreateElement("", "Root", "");

      xmldoc.AppendChild(xmlroot);
      
      {
        // Add meta data
        xmlmetaroot = xmldoc.CreateElement("", "MetaData", "");
        xmlroot.AppendChild(xmlmetaroot);

        // Title 
        xmlchild = xmldoc.CreateElement("", "Title", "");
        xmlchild.AppendChild(xmldoc.CreateTextNode(title));
        xmlmetaroot.AppendChild(xmlchild);

        // Author
        xmlchild = xmldoc.CreateElement("", "Author", "");
        xmlchild.AppendChild(xmldoc.CreateTextNode(author));
        xmlmetaroot.AppendChild(xmlchild);

        // Date
        xmlchild = xmldoc.CreateElement("", "Date", "");
        xmlchild.AppendChild(xmldoc.CreateTextNode(System.DateTime.Now.ToString()));
        xmlmetaroot.AppendChild(xmlchild);

        // OS information
        xmlchild = xmldoc.CreateElement("", "OperatingSystem", "");
        xmlchild.AppendChild(xmldoc.CreateTextNode(System.Environment.OSVersion.ToString()));
        xmlmetaroot.AppendChild(xmlchild);

        // Memory Installed
        xmlchild = xmldoc.CreateElement("", "MemoryInstalled", "");
        xmlchild.SetAttribute("Units", "MB");
        xmlchild.AppendChild(xmldoc.CreateTextNode(MachineInfo.PhysicalMemoryInstalled().ToString()));
        xmlmetaroot.AppendChild(xmlchild);

        // CPU
        xmlchild = xmldoc.CreateElement("", "CPU", "");
        xmlchild.AppendChild(xmldoc.CreateTextNode(MachineInfo.Processor("Name")));
        xmlmetaroot.AppendChild(xmlchild);

        // CurrentClockSpeed
        xmlchild = xmldoc.CreateElement("", "CurrentClockSpeed", "");
        xmlchild.SetAttribute("Units", "MHz");
        xmlchild.AppendChild(xmldoc.CreateTextNode(MachineInfo.Processor("CurrentClockSpeed")));
        xmlmetaroot.AppendChild(xmlchild);

        // MaxClockSpeed
        xmlchild = xmldoc.CreateElement("", "MaxClockSpeed", "");
        xmlchild.SetAttribute("Units", "MHz");
        xmlchild.AppendChild(xmldoc.CreateTextNode(MachineInfo.Processor("MaxClockSpeed")));
        xmlmetaroot.AppendChild(xmlchild);
      }
    }
  }

  // ----------------------------------------------------------------
  // Class to collection information about hardware for benchmarks

  public class MachineInfo {
    private static List<PropertyDataCollection> WMIQuery(string queryObject) {
      List<PropertyDataCollection> list = new List<PropertyDataCollection>();
      ManagementObjectSearcher searcher;
      try {
        searcher = new ManagementObjectSearcher("SELECT * FROM " + queryObject);
        foreach (ManagementObject wmi_HD in searcher.Get()) {
          list.Add(wmi_HD.Properties);
        }
      } catch {
        return null;
      }
      return list;
    }

    public static long PhysicalMemoryInstalled() {
      List<PropertyDataCollection> pdcl = WMIQuery("Win32_PhysicalMemory");
      long total = 0;
      if (pdcl != null) {
        foreach (PropertyDataCollection pdc in pdcl) {
          foreach (PropertyData pd in pdc) {
            if (pd.Name.Equals("Capacity"))
              total += long.Parse(pd.Value.ToString());
          }
        }
      }
      return total / (1024 * 1024);
    }

    public static String Processor(String q) {
      List<PropertyDataCollection> pdcl = WMIQuery("Win32_Processor");
      foreach (PropertyDataCollection pdc in pdcl) {
        foreach (PropertyData pd in pdc) {
          if (pd.Name.Equals(q))
            return pd.Value.ToString().Trim();
        }
      }
      return null;
    }

    public static String ProcessorInformation() {
      List<PropertyDataCollection> pdcl = WMIQuery("Win32_Processor");
      List<String> interestingProperties = new List<string>();

      interestingProperties.Add("Name");
      interestingProperties.Add("CurrentClockSpeed");
      interestingProperties.Add("MaxClockSpeed");

      StringBuilder sb = new StringBuilder();
      if (pdcl != null) {
        foreach (String property in interestingProperties) {
          String value = Processor(property);
          if (value != null) {
            sb.Append("   ");
            sb.Append(property);
            sb.Append(": ");
            sb.Append(value);
            sb.AppendLine();
          }
        }
      }
      return sb.ToString();
    }
  }

  // Compiling and running complete C# scripts or fragments, with reference to CoreCalc

  // TinyScript is primarily meant for test purposes. Instead of writing yet another
  // script language we use C# and reference functions in TinyCalc through a defined API.
  // This descission made it trival to add "script" functionality to TinyCalc. We harness the
  // power of C# throught the simplicity of reflexion. A major drawback however are that
  // C# is not the best suited computer language for modeling spreadsheet testcases. A script
  // with a more "spreadsheet like" syntax could have made the tests more clear.
  //
  // See Thomas Iversen's thesis for the definition of the TinyScript API.

  public class TinyScript {
    private readonly String src;
    private readonly bool file;
    private CompilerResults cr;

    public TinyScript(String src, bool file) {
      this.src = src;
      this.file = file;
      this.cr = null;
    }

    // This Stub functions provides a skeleton C# program with
    // a static main function (with argument array). src is 
    // assumed to be the methodbody of the built Main function.
    // The idea is that it should be easy to provide a small script on
    // the command line, without writing all the tedious setup code.
    
    private String Stub(String src) {
      StringBuilder sb = new StringBuilder();
      sb.Append("using System;");
      sb.Append("using System.Collections.Generic;");
      sb.Append("using System.Text;");
      sb.Append("namespace TinyScript {");
      sb.Append("   static class program {");
      sb.Append("       static void Main(String[] args) {");
      sb.Append(src);
      sb.Append("       }");
      sb.Append("   }");
      sb.Append("}");
      return sb.ToString();
    }

    private void Compile() {
      // Obtain an ICodeCompiler from a CodeDomProvider class.       
      CSharpCodeProvider provider = new CSharpCodeProvider();

      // Build the parameters for source compilation.
      CompilerParameters cp = new CompilerParameters();

      // Add an assembly reference.
      cp.ReferencedAssemblies.Add("System.dll");
      cp.ReferencedAssemblies.Add("System.Xml.dll");
      cp.ReferencedAssemblies.Add("System.Drawing.dll");
      cp.ReferencedAssemblies.Add(typeof(Workbook).Assembly.Location);

      // Generate in memory
      cp.GenerateExecutable = false;
      cp.GenerateInMemory = true;

      // Invoke compilation.
      if (file) {
        try {
          cr = provider.CompileAssemblyFromFile(cp, src);
        } catch (System.IO.FileNotFoundException) {
          Console.WriteLine("Could not find a file named {0}", src);
          return;
        }
      } else {
        cr = provider.CompileAssemblyFromSource(cp, Stub(src));
      }

      if (cr.Errors.Count > 0) {
        // Display compilation errors.
        Console.WriteLine("Errors compiling script. The following errors were returned: ");
        foreach (CompilerError ce in cr.Errors) {
          Console.WriteLine("  {0}", ce.ToString());
          Console.WriteLine();
        }
      } else {
        Console.WriteLine("Script built.");
      }
    }

    private void Execute(String[] args) {
      if (cr != null && cr.Errors.Count == 0) {
        Assembly a = cr.CompiledAssembly;

        // Find all types implemnted in this assembly.
        Module[] mods = a.GetModules(false);
        Type[] types = mods[0].GetTypes();

        try {
          // For each type see if it contains a static Main method.
          foreach (Type type in types) {
            MethodInfo mi = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
            if (mi != null) {
              if (mi.GetParameters().Length == 1) {
                if (mi.GetParameters()[0].ParameterType.IsArray) {
                  Console.WriteLine("Main method found (with arguments).");
                  Console.WriteLine("Running script.");
                  Console.WriteLine();
                  if (args != null) {
                    mi.Invoke(null, new Object[1] { args });
                  } else {
                    Console.WriteLine("No arguments given, providing dummy");
                    mi.Invoke(null, new string[1]);
                  }
                }
              } else {
                Console.WriteLine("Main method found (without arguments).");
                Console.WriteLine("Running script.");
                Console.WriteLine();
                mi.Invoke(null, null);
              }
            }
          }
        } catch (Exception ex) {
          Console.WriteLine("Script threw an exception");
          Console.WriteLine("Message: {0}", ex.Message);
          Console.WriteLine("InnerException: {0}", ex.InnerException.ToString());
          //throw ex;
        }
      }
      Console.WriteLine("Script terminated");
    }

    public void Run(String[] args) {
      Compile();
      Execute(args);
    }
  }
}
